package com.wiscamp.ninechapters.challenges.domain.services;

import com.wiscamp.ninechapters.challenges.domain.models.UserPoints;
import com.wiscamp.ninechapters.challenges.domain.models.UserPointsMonth;
import com.wiscamp.ninechapters.challenges.domain.models.UserPointsWeek;
import com.wiscamp.ninechapters.challenges.domain.repositories.PointsRepository;
import cube.ddd.aspect.AopServiceException;
import com.wiscamp.ninechapters.common.core.DateTimeHelper;
import com.wiscamp.ninechapters.ddd.DomainService;
import com.wiscamp.ninechapters.panels.domain.models.UserWechat;
import com.wiscamp.ninechapters.panels.domain.services.WechatUserService;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.Collection;
import java.util.HashMap;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * Points Service
 */
@Service
public class PointsService implements DomainService {
    private Logger logger = LoggerFactory.getLogger(this.getClass());


    @Autowired
    private PointsRepository pointsRepository;

    @Autowired
    private WechatUserService wechatUserService;

    public PointsService(PointsRepository pointsRepository, WechatUserService wechatService) {
        this.pointsRepository = pointsRepository;
        this.wechatUserService = wechatService;
    }

    /**
     * Gets user's points and rank
     *
     * @param userId
     * @return
     */

    @AopServiceException
    public UserPoints getUserPoints(@Min(0) long userId) {
        var userPoints = pointsRepository.getUserPointsByUser(userId);
        if (Objects.isNull(userPoints)) {
            // 如果积分排名信息不存在，则初始化
            userPoints = new UserPoints();
            userPoints.setHearts(9);
            userPoints.setLevel(1);
            userPoints.setUserId(userId);
            userPoints = pointsRepository.saveUserPoints(userPoints);

            // 新人授予“初来乍到”勋章
            // medalService.awardMedal(userId, "MEDAL_NEWBIE");
        }
        return userPoints;
    }

    /**
     * Gets user's points and rank monthly
     *
     * @param userId
     * @return
     */

    @AopServiceException
    public UserPointsMonth getUserPointsMonth(@Min(0) long userId) {
        var today = LocalDate.now();
        var userPointsMonth = pointsRepository.getUserPointsMonthByUser(userId,
                today.getYear(), today.getMonthValue());
        if (Objects.isNull(userPointsMonth)) {
            userPointsMonth = new UserPointsMonth();
            userPointsMonth.setDateYear(today.getYear());
            userPointsMonth.setDateMonth(today.getMonthValue());
            userPointsMonth.setUserId(userId);
            userPointsMonth = pointsRepository.saveUserPointsMonth(userPointsMonth);
        }
        return userPointsMonth;
    }

    /**
     * Gets user's points and rank weekly
     *
     * @param userId
     * @return
     */

    @AopServiceException
    public UserPointsWeek getUserPointsWeek(@Min(0) long userId) {
        var today = LocalDate.now();
        int week = DateTimeHelper.getWeekOfYear(today);
        var userPointsWeek = pointsRepository.getUserPointsWeekByUser(userId,
                today.getYear(), week);
        if (userPointsWeek == null) {
            userPointsWeek = new UserPointsWeek();
            userPointsWeek.setDateYear(today.getYear());
            userPointsWeek.setWeekOfYear(week);
            userPointsWeek.setUserId(userId);
            userPointsWeek = pointsRepository.saveUserPointsWeek(userPointsWeek);
        }
        return userPointsWeek;
    }

    /**
     * Updates user's points and rank
     *
     * @param userPoints
     * @param updateRank
     * @return
     */

    @AopServiceException
    public UserPoints updateUserPoints(@NotBlank UserPoints userPoints, boolean updateRank) {
        var savedUserPoints = pointsRepository.saveUserPoints(userPoints);
        if (updateRank) {
            resortUserPoints();
        }
        return savedUserPoints;
    }

    /**
     * Resorts user's rank by points
     */
    @AopServiceException
    public void resortUserPoints() {
        var sort = Sort.by(Sort.Direction.DESC, "totalPoints");
        var pageable = PageRequest.of(0, 100, sort);
        var sortedUserPoints = pointsRepository.getTop100OrderByPoints(pageable);
        int counter = 0;
        for (var userPoints : sortedUserPoints) {
            userPoints.setTotalRank(counter + 1);
            counter++;
        }
        pointsRepository.saveUserPointsList(sortedUserPoints);
        if (sortedUserPoints.size() == 100) {
            Collection<Long> itemIds = sortedUserPoints.stream().map(UserPoints::getUserId)
                    .collect(Collectors.toList());
            pointsRepository.rankingAfter100ByPoints(itemIds);
        }
    }

    /**
     * Resorts user's month rank by points
     */
    @AopServiceException
    public void resortUserPointsMonth() {
        var now = LocalDate.now();

        var sort = Sort.by(Sort.Direction.DESC, "monthPoints");
        var pageable = PageRequest.of(0, 100, sort);
        var sortedUserPointsMonth = pointsRepository.getTop100OrderByMonthPoints(now.getYear(),
                now.getMonthValue(), pageable);
        int counter = 0;
        for (var userPoints : sortedUserPointsMonth) {
            userPoints.setMonthRank(counter + 1);
            counter++;
        }
        pointsRepository.saveUserPointsMonthList(sortedUserPointsMonth);
        if (sortedUserPointsMonth.size() == 100) {
            Collection<Long> userIds = sortedUserPointsMonth.stream().map(UserPointsMonth::getUserId)
                    .collect(Collectors.toList());
            pointsRepository.rankingAfter100ByPoints(userIds);
        }
    }

    /**
     * Resorts user's week rank by points
     */
    @AopServiceException
    public void resortUserPointsWeek() {
        var now = LocalDate.now();
        int week = DateTimeHelper.getWeekOfYear(now);
        var sort = Sort.by(Sort.Direction.DESC, "weekPoints");
        var pageable = PageRequest.of(0, 100, sort);
        var sortedUserPointsWeek = pointsRepository.getTop100OrderByWeekPoints(now.getYear(),
                week, pageable);
        int counter = 0;
        for (var userPoints : sortedUserPointsWeek) {
            userPoints.setWeekRank(counter + 1);
            counter++;
        }
        pointsRepository.saveUserPointsWeekList(sortedUserPointsWeek);
        if (sortedUserPointsWeek.size() == 100) {
            Collection<Long> itemIds = sortedUserPointsWeek.stream().map(UserPointsWeek::getUserId)
                    .collect(Collectors.toList());
            pointsRepository.rankingAfter100ByPoints(itemIds);
        }
    }

    /**
     * Updates user's month rank
     *
     * @param userPointsMonth
     * @return
     */

    @AopServiceException
    public UserPointsMonth updateUserPointsMonth(@NotNull UserPointsMonth userPointsMonth) {
        return pointsRepository.saveUserPointsMonth(userPointsMonth);
    }

    /**
     * Updates user's week rank
     *
     * @param userPointsWeek
     * @return
     */

    @AopServiceException
    public UserPointsWeek updateUserPointsWeek(@NotNull UserPointsWeek userPointsWeek) {
        return pointsRepository.saveUserPointsWeek(userPointsWeek);
    }

    /**
     * Gets a report of daily challenge
     *
     * @param taskId
     * @return
     */
    /*@AopServiceException
    public ChallengeReportDTO getBattleReport(@NotNull DistributionTaskView taskView, @NotNull TaskReport report) {
        // don't award points while task is not a battle task
        if (!taskView.getSetCode().contains("DC-")) {
            var dto = new ChallengeReportDTO();
            return dto;
        }

        Collection<TaskReportItem> reportItems = report.getReportItems();
        int totalGainPoints = 0;
        int gainHurt = 0;
        int totalCorrectQty = 0;
        int counter = 0;
        var dto = new ChallengeReportDTO();
        for (var reportItem : reportItems) {
            int problemPoints = 0;
            int problemHurt = 0;
            boolean problemCorrect = false;
            int problemSpend = reportItem.getItemSpendSeconds();
            if (reportItem.isCorrect()) {
                problemCorrect = true;
                totalCorrectQty++;
                // 答题正确默认获得1积分，做题时间低于3分钟额外获得2积分，做题时间低于5分钟额外获取2积分
                problemPoints = 1;
                long spendSeconds = reportItem.getItemSpendSeconds();
                if (spendSeconds > 0) {
                    if (spendSeconds <= 180)
                        problemPoints = 3;
                    else if (spendSeconds <= 300) {
                        problemPoints = 2;
                    }
                }
                totalGainPoints = totalGainPoints + problemPoints;
            } else {
                problemHurt = 1;
                gainHurt = gainHurt + 1;
            }

            ProblemTupleDTO<Boolean, Integer, Integer, Integer> tuple = new ProblemTupleDTO<>(problemCorrect,
                    problemPoints, problemHurt, problemSpend);
            dto.getProblemPoints().put(counter + 1, tuple);
            counter++;
        }
        int gainHearts = totalCorrectQty / 2;
        // 三题全部中却，额外奖励1点heart
        if (totalCorrectQty == 3)
            gainHearts++;


        dto.setTotalGainHearts(gainHearts - gainHurt);
        dto.setTotalGainPoints(totalGainPoints);
        dto.setGainHurt(gainHurt);
        dto.setRewardHearts(gainHearts);
        return dto;
    }*/

    /**
     * Calculates user's points
     *
     * @param userId
     * @param pointQty
     * @param heartQty
     * @param hurt
     */

    public UserPoints calculatePersonalPoints(@Min(0) long userId, int pointQty, int heartQty, int hurt) {
        var userPoints = getUserPoints(userId);
        if (Objects.isNull(userPoints))
            return null;
        int hearts = userPoints.getHearts();
        hearts = hearts + heartQty - hurt;
        if (hearts > 9)
            hearts = 9;
        if (hearts < 0)
            hearts = 0;
        userPoints.setHearts(hearts);
        int points = userPoints.getTotalPoints();
        points = points + pointQty;
        userPoints.setTotalPoints(points);
        int level = points / 10;
        if (level == 0)
            level = 1;
        userPoints.setLevel(level);
        updateUserPoints(userPoints, true);
        calculatePointWeek(userId, pointQty);
        calculatePointMonth(userId, pointQty);
        return userPoints;
    }


    /**
     * Calculates user's week points
     *
     * @param userId
     * @param pointQty
     */

    private void calculatePointWeek(@Min(0) long userId, int pointQty) {
        var userPointsWeek = getUserPointsWeek(userId);
        if (userPointsWeek == null)
            return;
        // DateTime today = DateTime.now();
        /*
         * // 如果年度周发生变化，则先清除原周积分和排名 if (today.getYear() != userPointsWeek.getDateYear()
         * || today.getWeekOfWeekyear() != userPointsWeek.getWeekOfYear()) {
         * userPointsWeek.setWeekOfYear(today.getWeekOfWeekyear());
         * userPointsWeek.setDateYear(today.getYear()); userPointsWeek.setWeekPoints(0);
         * userPointsWeek.setWeekRank(0); }
         */
        userPointsWeek.setWeekPoints(userPointsWeek.getWeekPoints() + pointQty);
        // recalculate week rank
        // userPointsWeek.setWeekRank(0);
        updateUserPointsWeek(userPointsWeek);
    }

    /**
     * Calculates user's month points
     *
     * @param userId
     * @param pointQty
     */

    private void calculatePointMonth(@Min(0) long userId, int pointQty) {
        var userPointsMonth = getUserPointsMonth(userId);
        if (userPointsMonth == null)
            return;
        // DateTime today = DateTime.now();
        /*
         * // 如果年度周发生变化，则先清除原周积分和排名 if (today.getYear() != userPointsMonth.getDateYear()
         * || today.getMonthOfYear() != userPointsMonth.getDateMonth()) {
         * userPointsMonth.setDateMonth(today.getMonthOfYear());
         * userPointsMonth.setDateYear(today.getYear());
         * userPointsMonth.setMonthPoints(0); userPointsMonth.setMonthRank(0); }
         */
        userPointsMonth.setMonthPoints(userPointsMonth.getMonthPoints() + pointQty);
        // recalculate month rank
        // userPointsMonth.setMonthRank(0);
        updateUserPointsMonth(userPointsMonth);
    }

    /**
     * Gets top 100 user's rank
     *
     * @return
     */

    @Cacheable(value = "rank-user", key = "1")
    @AopServiceException
    public Collection<UserPoints> getTop100UsersPoints() {
        var sort = Sort.by(Sort.Direction.ASC, "totalPoints");
        var pageable = PageRequest.of(0, 100, sort);
        var userPointsList = pointsRepository.getTop100OrderByPoints(pageable);
        Collection<Long> userIds = userPointsList.stream().map(UserPoints::getUserId).collect(Collectors.toList());
        Collection<UserWechat> userWechatList = wechatUserService.getUserWechatByIds(userIds);
        HashMap<Long, UserWechat> map = new HashMap<>();
        userWechatList.forEach(userWechat -> map.put(userWechat.getUserId(), userWechat));
        userPointsList.forEach(u -> {
            var userWechat = map.get(u.getUserId());
            if (userWechat != null) {
                u.setNickName(userWechat.getNickName());
                u.setAvatarUrl(userWechat.getAvatarUrl());
            }
        });
        return userPointsList;
    }

    /**
     * Gets top 100 user's month rank
     *
     * @return
     */

    @Cacheable(value = "rank-user-month", key = "1")
    @AopServiceException
    public Collection<UserPointsMonth> getTop100UsersPointsMonth() {
        resortUserPointsMonth();
        var now = LocalDate.now();
        var sort = Sort.by(Sort.Direction.ASC, "monthPoints");
        var pageable = PageRequest.of(0, 100, sort);
        var userPointsMonthList = pointsRepository.getTop100OrderByMonthPoints(now.getYear(),
                now.getMonthValue(), pageable);
        Collection<Long> userIds = userPointsMonthList.stream().map(UserPointsMonth::getUserId).collect(Collectors.toList());
        Collection<UserWechat> userWechatList = wechatUserService.getUserWechatByIds(userIds);
        HashMap<Long, UserWechat> map = new HashMap<>();
        userWechatList.forEach(userWechat -> map.put(userWechat.getUserId(), userWechat));
        userPointsMonthList.forEach(u -> {
            var userWechat = map.get(u.getUserId());
            if (userWechat != null) {
                u.setNickName(userWechat.getNickName());
                u.setAvatarUrl(userWechat.getAvatarUrl());
            }
        });
        return userPointsMonthList;
    }

    /**
     * Gets top 100 user's week rank
     *
     * @return
     */

    @Cacheable(value = "rank-user-week", key = "1")
    @AopServiceException
    public Collection<UserPointsWeek> getTop100UsersPointsWeek() {
        resortUserPointsWeek();
        var now = LocalDate.now();
        int week = DateTimeHelper.getWeekOfYear(now);
        var sort = Sort.by(Sort.Direction.DESC, "weekPoints");
        var pageable = PageRequest.of(0, 100, sort);
        var userPointsWeekList = pointsRepository.getTop100OrderByWeekPoints(now.getYear(),
                week, pageable);

        Collection<Long> userIds = userPointsWeekList.stream().map(UserPointsWeek::getUserId).collect(Collectors.toList());
        Collection<UserWechat> userWechatList = wechatUserService.getUserWechatByIds(userIds);
        HashMap<Long, UserWechat> map = new HashMap<>();
        userWechatList.forEach(userWechat -> map.put(userWechat.getUserId(), userWechat));
        userPointsWeekList.forEach(u -> {
            var userWechat = map.get(u.getUserId());
            if (userWechat != null) {
                u.setNickName(userWechat.getNickName());
                u.setAvatarUrl(userWechat.getAvatarUrl());
            }
        });
        return userPointsWeekList;
    }


    public void updateGroupPoints(long managerId) {
        // 更新用户积分信息中组队标志
        var userPoints = getUserPoints(managerId);
        if (Objects.nonNull(userPoints)) {
            userPoints.setHasGroup(true);
            updateUserPoints(userPoints, false);
        }
    }

    /**
     * Adds one heart for user
     *
     * @param userId
     */

    @AopServiceException
    public void addHeart(@Min(0) long userId) {
        var userPoints = getUserPoints(userId);
        if (userPoints != null) {
            int heart = userPoints.getHearts();
            heart = heart + 1;
            if (heart > 9)
                heart = 9;
            userPoints.setHearts(heart);
            updateUserPoints(userPoints, false);
        }
    }
}
